import Obj from "./Obj";
import Collection from "./Collection";

/**
 * 计算一批对象的hashCode。
 *
 * 算法会依次计算每个对象的hashCode并进行相应的操作。
 *
 * @param hashing 需要计算hashCode的对象组
 * @return {number} 该对象组的hashCode
 * @see hashSingle
 * @version 1.0
 */
export function hash(...hashing: any[]): number {
  return hashArray(hashing);
}

export function hashArray(hashing: any[] | Collection<any>): number {
  let result = 1;

  for (let element of hashing) {
    result = 31 * result + hashSingle(element);
  }

  return result;
}

/**
 * 计算一个对象的hashcode。
 *
 * 若此对象已经继承了{@link O}，则会调用其{@link O#hashCode}以获得。
 * 若此对象为number，则会直接返回其数字。
 * 若此对象为boolean，则会直接返回其布尔值对应的数字。（即true时为1，false时为0）。
 * 否则会将该对象转为string并进行计算。
 *
 * 由于hash算法仅为一种摘要算法，因此当两个对象获得相同的hashCode时，并不代表两者一定相同。所以我们推荐您在继承{@link O}以及其扩展类时，手动重写
 * 其{@link O#hashCode}方法让其结果更加有分辨价值。至少应该保证在复杂类型中，其hashCode应该精确到基础类型（Number，String和Boolean）。
 *
 * @param hashing 需要转为hashCode的对象
 * @return {number} 该对象的hashCode
 * @version 1.0
 */
export function hashSingle(hashing: any): number {
  if (hashing instanceof Obj) {
    return hashing.hashCode();
  } else if (hashing === null || hashing === undefined) {
    return 0;
  }else if (typeof hashing === "number") {
    return hashing;
  } else if (typeof hashing === "boolean") {
    return Number(hashing);
  } else {
    const str = JSON.stringify(hashing);
    let h = 0;
    if (str.length === 0) return h;
    for (let i = 0; i < str.length; i++) {
      let chr = str.charCodeAt(i);
      h = ((h << 5) - h) + chr;
      h |= 0;
    }
    return h;
  }
}
